﻿using System.Collections.Generic;

using Godot.core.io;
using Godot.core;

namespace Godot.modules.gdscript
{
    public partial class GDCompiler
    {
        protected void _set_error(string p_error, GDParser.Node p_node)
        {
            if (_error != "")
                return;

            _error = p_error;
            _err_line = p_node.line;
            _err_column = p_node.column;
        }

        protected int _parse_assign_right_expression(ref CodeGen codegen, GDParser.OperatorNode p_expression, int p_stack_level) 
        {
	        Variant.Operator var_op = Variant.Operator.OP_MAX;

	        switch(p_expression.op) 
            {
		        case GDParser.OperatorNode.Operator.OP_ASSIGN_ADD: var_op = Variant.Operator.OP_ADD; break;
		        case GDParser.OperatorNode.Operator.OP_ASSIGN_SUB: var_op = Variant.Operator.OP_SUBSTRACT; break;
		        case GDParser.OperatorNode.Operator.OP_ASSIGN_MUL: var_op = Variant.Operator.OP_MULTIPLY; break;
		        case GDParser.OperatorNode.Operator.OP_ASSIGN_DIV: var_op = Variant.Operator.OP_DIVIDE; break;
		        case GDParser.OperatorNode.Operator.OP_ASSIGN_MOD: var_op = Variant.Operator.OP_MODULE; break;
		        case GDParser.OperatorNode.Operator.OP_ASSIGN_SHIFT_LEFT: var_op = Variant.Operator.OP_SHIFT_LEFT; break;
		        case GDParser.OperatorNode.Operator.OP_ASSIGN_SHIFT_RIGHT: var_op = Variant.Operator.OP_SHIFT_RIGHT; break;
		        case GDParser.OperatorNode.Operator.OP_ASSIGN_BIT_AND: var_op = Variant.Operator.OP_BIT_AND; break;
		        case GDParser.OperatorNode.Operator.OP_ASSIGN_BIT_OR: var_op = Variant.Operator.OP_BIT_OR; break;
		        case GDParser.OperatorNode.Operator.OP_ASSIGN_BIT_XOR: var_op = Variant.Operator.OP_BIT_XOR; break;
		        case GDParser.OperatorNode.Operator.OP_ASSIGN: {
			        //none
		        } break;
	            
                default: {
			        return(-1);
		        }
	        }

	        if (var_op == Variant.Operator.OP_MAX) 
            {

		        return _parse_expression(ref codegen,p_expression.arguments[1], p_stack_level);
	        }

	        if (!_create_binary_operator(ref codegen, p_expression, var_op, p_stack_level))
		        return -1;

	        int dst_addr = (p_stack_level) | ((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS);
	        codegen.opcodes.Add(dst_addr); // append the stack level as destination address of the opcode
	        codegen.alloc_stack(p_stack_level);
	        return dst_addr;

        }

        protected Error _parse_class(GDScript p_script, GDScript p_owner, GDParser.ClassNode p_class)
        {
            p_script._native = new GDNativeClass();
            p_script._basescr = new GDScript();
            p_script._base = null;
            p_script._members = new SortedSet<StringName>();
            p_script._constants = new Dictionary<StringName, Variant>();
            p_script._member_functions = new Dictionary<StringName, GDFunction>();
            p_script._member_indices = new Dictionary<StringName, int>();
            p_script._member_info = new Dictionary<StringName, PropertyInfo>();
            p_script._initializer = null;
            p_script._subclasses = new Dictionary<StringName, GDScript>();
            p_script._owner = p_owner;
            p_script._tool = p_class.tool;
            p_script._name = p_class.name;

            int index_from = 0;

            if (p_class.extends_used)
            {
                //do inheritance
                string path = p_class.extends_file;

                GDScript script = null;
                GDNativeClass native = null;

                if (path != "")
                {
                    //path (and optionally subclasses)

                    script = (GDScript)ResourceLoader.load(path);

                    if (script == null)
                    {
                        _set_error("Could not load base class: " + path, p_class);
                        return Error.ERR_FILE_NOT_FOUND;
                    }

                    if (p_class.extends_class.Count != 0)
                    {
                        for (int i = 0; i < p_class.extends_class.Count; i++)
                        {
                            string sub = p_class.extends_class[i];
                            if (script._subclasses.ContainsKey(sub))
                            {
                                script = script._subclasses[sub];
                            }
                            else
                            {
                                _set_error("Could not find subclass: " + sub, p_class);
                                return Error.ERR_FILE_NOT_FOUND;
                            }
                        }
                    }

                }
                else
                {

                    if (p_class.extends_class.Count == 0)
                        return Error.ERR_BUG;

                    //look around for the subclasses
                    string base_ = p_class.extends_class[0];
                    GDScript p = p_owner;
                    GDScript base_class = null;

                    while (p != null)
                    {
                        if (p._subclasses.ContainsKey(base_))
                        {
                            base_class = p._subclasses[base_];
                            break;
                        }
                        p = p._owner;
                    }

                    if (base_class != null)
                    {
                        for (int i = 1; i < p_class.extends_class.Count; i++)
                        {
                            string subclass = p_class.extends_class[i];

                            if (base_class._subclasses.ContainsKey(subclass))
                            {
                                base_class = base_class._subclasses[subclass];
                            }
                            else
                            {
                                _set_error("Could not find subclass: " + subclass, p_class);
                                return Error.ERR_FILE_NOT_FOUND;
                            }
                        }
                        script = base_class;
                    }
                    else
                    {
                        if (p_class.extends_class.Count > 1)
                        {
                            _set_error("Invalid inheritance (unknown class+subclasses)", p_class);
                            return Error.ERR_FILE_NOT_FOUND;

                        }
                        //if not found, try engine classes
                        if (!GDScriptLanguage.singleton.get_global_map().ContainsKey(base_))
                        {
                            _set_error("Unknown class: '" + base_ + "'", p_class);
                            return Error.ERR_FILE_NOT_FOUND;
                        }

                        int base_idx = GDScriptLanguage.singleton.get_global_map()[base_];
                        native = (GDNativeClass)GDScriptLanguage.singleton.get_global_array()[base_idx];
                        if (native == null)
                        {
                            _set_error("Global not a class: '" + base_ + "'", p_class);
                            return Error.ERR_FILE_NOT_FOUND;
                        }
                    }
                }

                if (script != null)
                {
                    p_script._basescr = script;
                    p_script._base = p_script._basescr;
                    p_script._member_indices = script._member_indices;

                }
                else if (native != null)
                {
                    p_script._native = native;
                }
                else
                {
                    _set_error("Could not determine inheritance", p_class);
                    return Error.ERR_FILE_NOT_FOUND;
                }
            }

            for (int i = 0; i < p_class.variables.Count; i++)
            {

                StringName name = p_class.variables[i].identifier;
                if (p_script._member_indices.ContainsKey(name))
                {
                    _set_error("Member '" + name + "' already exists (in current or parent class)", p_class);
                    return Error.ERR_ALREADY_EXISTS;
                }

                if (p_class.variables[i].export.type != Variant.Type.NIL)
                {
                    p_script._member_info[name] = p_class.variables[i].export;
                }

                int new_idx = p_script._member_indices.Count;
                p_script._member_indices[name] = new_idx;
                p_script._members.Add(name);

            }

            for (int i = 0; i < p_class.constant_expressions.Count; i++)
            {
                StringName name = p_class.constant_expressions[i].identifier;
                if (p_class.constant_expressions[i].expression.type != GDParser.Node.Type.TYPE_CONSTANT)
                    continue;

                GDParser.ConstantNode constant = (GDParser.ConstantNode)(p_class.constant_expressions[i].expression);

                p_script._constants.Add(name, constant.value);
                //p_script->constants[constant->value].make_const();
            }

            //parse sub-classes
            for (int i = 0; i < p_class.subclasses.Count; i++)
            {
                StringName name = p_class.subclasses[i].name;

                GDScript subclass = new GDScript();

                Error err = _parse_class(subclass, p_script, p_class.subclasses[i]);
                if (err != null)
                    return err;
                p_script._subclasses.Add(name, subclass);

            }


            //parse methods
            bool has_initializer = false;
            for (int i = 0; i < p_class.functions.Count; i++)
            {
                if (!has_initializer && p_class.functions[i].name == "_init")
                    has_initializer = true;
                Error err = _parse_function(p_script, p_class, p_class.functions[i]);
                if (err != null)
                    return err;
            }

            //parse static methods
            for (int i = 0; i < p_class.static_functions.Count; i++)
            {

                Error err = _parse_function(p_script, p_class, p_class.static_functions[i]);
                if (err != null)
                    return err;
            }


            if (!has_initializer)
            {
                //create a constructor
                Error err = _parse_function(p_script, p_class, null);
                if (err != null)
                    return err;
            }

            return Error.OK;
        }

        protected Error _parse_function(GDScript p_script, GDParser.ClassNode p_class, GDParser.FunctionNode p_func)
        {
            List<int> bytecode = new List<int>();
	        CodeGen codegen = new CodeGen();

	        codegen.class_node = p_class;
	        codegen.script = p_script;
	        codegen.function_node = p_func;
	        codegen.stack_max = 0;
	        codegen.current_line = 0;
	        codegen.call_max = 0;
	        //codegen.debug_stack = ScriptDebugger::get_singleton()!=NULL;

	        int stack_level = 0;

            if (p_func != null)
            {
                for (int i = 0; i < p_func.arguments.Count; i++)
                {
                    int idx = i;
                    codegen.add_stack_identifier(p_func.arguments[i], i);
                }
                stack_level = p_func.arguments.Count;
            }

            codegen.alloc_stack(stack_level);

	        /* Parse initializer -if applies- */

	        bool is_initializer = false || (p_func == null);

	        if (p_func == null || (string)(p_func.name) == "_init") 
            {
		        //parse initializer for class members
		        if (p_func == null && p_class.extends_used && p_script._native == null)
                {
			        //call implicit parent constructor
			        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_CALL_SELF_BASE);
			        codegen.opcodes.Add(codegen.get_name_map_pos("_init"));
			        codegen.opcodes.Add(0);
			        codegen.opcodes.Add(((int)GDFunction.Address.ADDR_TYPE_STACK << (int)GDFunction.Address.ADDR_BITS)| 0);

		        }
		        Error err = _parse_block(ref codegen, p_class.initializer, stack_level);
		        if (err != null)
			        return err;

		        is_initializer=true;
	        }

            /* Parse default argument code -if applies- */
	        List<int> defarg_addr  = new List<int>();
	        StringName func_name = "";

	        if (p_func != null) 
            {
		        if (p_func.default_values.Count != 0) 
                {
			        codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_JUMP_TO_DEF_ARGUMENT);
			        defarg_addr.Add(codegen.opcodes.Count);
			        for(int i = 0; i < p_func.default_values.Count; i++) 
                    {
				        _parse_expression(ref codegen, p_func.default_values[i], stack_level, true);
				        defarg_addr.Add(codegen.opcodes.Count);
			        }

			        defarg_addr.Reverse();
		        }

		        Error err = _parse_block(ref codegen, p_func.body, stack_level);
		        if (err != Error.OK)
			        return err;

		        func_name = p_func.name;
	        } 
            else 
            {
		        func_name="_init";
	        }

            codegen.opcodes.Add((int)GDFunction.Opcode.OPCODE_END);

	        GDFunction gdfunc = null;

	        //if (String(p_func->name)=="") { //initializer func
	        //	gdfunc = &p_script->initializer;

	        //} else { //regular func
		        p_script._member_functions[func_name] = new GDFunction();
		        gdfunc = p_script._member_functions[func_name];
	        //}

	        if (p_func != null)
		        gdfunc._static = p_func.is_static;

	        //constants
	        if (codegen.constant_map.Count != 0) 
            {
		        gdfunc._constant_count = codegen.constant_map.Count;
                gdfunc._constants = new List<Variant>(codegen.constant_map.Count);
		        gdfunc._constants_ptr = gdfunc._constants.ToArray();
		        Variant K = null;
                foreach (KeyValuePair<Variant, int> E in codegen.constant_map)
                {
                    int idx = E.Value;
                    gdfunc._constants[idx] = E.Key;
                }
	        } 
            else 
            {
		        gdfunc._constants_ptr = null;
		        gdfunc._constant_count = 0;
	        }

            //global names
	        if (codegen.name_map.Count != 0) 
            {
		        gdfunc._global_names = new List<StringName>(codegen.name_map.Count);
		        gdfunc._global_names_ptr = gdfunc._global_names.ToArray();
                foreach(KeyValuePair<StringName,int> E in codegen.name_map) 
                {
			        gdfunc._global_names[E.Value] = E.Key;
		        }
		        gdfunc._global_names_count = gdfunc._global_names.Count;

	        } 
            else 
            {
		        gdfunc._global_names_ptr = null;
		        gdfunc._global_names_count = 0;
	        }

            if (codegen.opcodes.Count != 0)
            {

                gdfunc._code = codegen.opcodes;
                gdfunc._code_ptr = gdfunc._code.ToArray();
                gdfunc._code_size = codegen.opcodes.Count;

            }
            else
            {
                gdfunc._code_ptr = null;
                gdfunc._code_size = 0;
            }

            if (defarg_addr.Count != 0)
            {

                gdfunc._default_arguments = defarg_addr;
                gdfunc._default_arg_count = defarg_addr.Count;
                gdfunc._default_arg_ptr = gdfunc._default_arguments.ToArray();
            }
            else
            {
                gdfunc._default_arg_count = 0;
                gdfunc._default_arg_ptr = null;
            }

            gdfunc._argument_count = (p_func != null) ? p_func.arguments.Count : 0;
            gdfunc._stack_size = codegen.stack_max;
            gdfunc._call_size = codegen.call_max;
            gdfunc.name = func_name;
            gdfunc._script = p_script;
            gdfunc.source = _source;
            if (p_func != null)
            {
                gdfunc._initial_line = p_func.line;
            }
            else
            {
                gdfunc._initial_line = 0;
            }

            //if (codegen.debug_stack)
            //    gdfunc._stack_debug = codegen.stack_debug;

            if (is_initializer)
                p_script._initializer = gdfunc;

            return Error.OK;
        }
    }
}
